#pragma once

#include <cstring>
#include <iostream>
#include <libusb-1.0/libusb.h>
#include <map>
#include <memory>
#include <string>
#include <usb.h>
#include <vector>

/*	BAUD rate setting	*/
#define B300 0x01
#define B600 0x02
#define B1200 0x03
#define B2400 0x04
#define B4800 0x05
#define B9600 0x06
#define B14400 0x07
#define B19200 0x08
#define B28800 0x09
#define B38400 0x0A
#define B57600 0x0B
#define B76800 0x0C
#define B115200 0x0D

/* Parity define	*/
#define P_ODD 0x01 // 奇校验
#define P_EVEN 0x02 // 偶校验
#define P_SPC 0x03 // 空白位
#define P_NONE 0x04 // 无校验

/*	Data bits define		*/
#define BIT_5 0x01
#define BIT_6 0x02
#define BIT_7 0x03
#define BIT_8 0x04

/* Stop bits define	*/
#define STOP_1 0x01
#define STOP_2 0x02

#define U8_TO_U16(a, b) ((((uint16_t)a) << 8) | ((uint16_t)b))

#define MAX_DEVICES 0x10
#define HID_RT_INPUT 0x01
#define HID_RT_OUTPUT 0x02
#define HID_REPORT_GET 0x01
#define HID_REPORT_SET 0x09

#define ERROR_CHECK(condition, ...) \
    if (condition) {                \
        printf(__VA_ARGS__);        \
        return false;               \
    }

class ModbusPacket {
public:
    enum FunCode_t : uint8_t {
        READ_REG = 0x03,
        WRITE_REG = 0x06,
    };

public:
    ModbusPacket() = default;
    ~ModbusPacket() = default;

    std::vector<uint8_t> readRegisterPack(uint8_t slaveAddr, uint16_t regAddr)
    {
        std::vector<uint8_t> pack;
        pack.emplace_back(slaveAddr);
        pack.emplace_back(READ_REG);
        pack.emplace_back(regAddr >> 8 & 0xff);
        pack.emplace_back(regAddr & 0xff);
        pack.emplace_back(0x00);
        pack.emplace_back(0x01);
        getCrc16(pack);
        return pack;
    }

    std::vector<uint8_t> writeRegisterPack(uint8_t slaveAddr, uint16_t regAddr, uint16_t val)
    {
        std::vector<uint8_t> pack;
        pack.emplace_back(slaveAddr);
        pack.emplace_back(WRITE_REG);
        pack.emplace_back(regAddr >> 8 & 0xff);
        pack.emplace_back(regAddr & 0xff);
        pack.emplace_back(val >> 8 & 0xff);
        pack.emplace_back(val & 0xff);
        getCrc16(pack);
        return pack;
    }

    bool checkCrc(std::vector<uint8_t> arr)
    {
        auto crc = U8_TO_U16(arr[arr.size() - 2], arr[arr.size() - 1]);
        arr.pop_back();
        arr.pop_back();
        return getCrc16(arr) == crc;
    }

private:
    uint16_t getCrc16(std::vector<uint8_t>& arr)
    {
        uint16_t crc = 0xFFFF;
        std::size_t length = arr.size();

        for (std::size_t i = 0; i < length; ++i) {
            crc ^= arr[i];
            for (std::size_t j = 0; j < 8; ++j) {
                if (crc & 0x0001) {
                    crc = (crc >> 1) ^ 0xA001;
                } else {
                    crc >>= 1;
                }
            }
        }

        arr.emplace_back(crc >> 8 & 0xff);
        arr.emplace_back(crc & 0xff);

        return crc;
    }
};

struct Point_t {
    uint16_t addr; /* 点地址 */
    float unit; /* 单位 */
    std::string dataType; /* 数据类型 */
    uint32_t rValue; /* 读到的缓存值 */
};

using PointTable_t = std::map<std::string, Point_t>;

struct UsbDevice_t {
    struct libusb_device* dev; /* 设备 */
    libusb_device_handle* handle; /* 句柄 */
    int outEndPoint; /* 输出端点 */
    int inEndPoint; /* 输入端点 */

    uint8_t slaveAddr; /* modbus地址 */
    std::string model; /* 设备型号 */
    std::string profilePath; /* 点表文件路径 */
    PointTable_t pointTable; /* 点表 */

    uint32_t readTimeout; /* 读超时 */
    uint32_t writeTimeout; /* 写超时 */

    UsbDevice_t()
    {
        dev = nullptr;
        handle = nullptr;
        outEndPoint = inEndPoint = 0;
        slaveAddr = 0x03;
        readTimeout = 1000;
        writeTimeout = 1000;
    }

    bool isOpen(void)
    {
        return handle != nullptr;
    }

    bool close(void)
    {
        dev = nullptr;
        handle = nullptr;
        return true;
    }
};

class UsbInterface {
private:
    UsbDevice_t _usbDevice;

public:
    UsbInterface()
    {
        auto ret = libusb_init(NULL);
        if (LIBUSB_SUCCESS != ret) {
            // TODO:添加log库
            printf("libusb initialization failed: %d\n", ret);
            return;
        }
    }

    ~UsbInterface() = default;

    bool findUsbDevice(int vid, int pid)
    {
        libusb_device* dev = nullptr;
        libusb_device** devs = nullptr;

        std::size_t i = 0;

        auto cnt = libusb_get_device_list(NULL, &devs);
        if (cnt < 0) {
            printf("failed to get device list\n");
            return false;
        } else if (0 == cnt) {
            printf("can't find usb device\n");
            return false;
        }

        while (nullptr != (dev = devs[i++])) {
            struct libusb_device_descriptor desc;
            auto ret = libusb_get_device_descriptor(dev, &desc);
            if (ret < 0) {
                printf("failed to get device descriptor\n");
                return false;
            }

            if ((desc.idProduct != pid) || (desc.idVendor != vid)) {
                continue;
            }

            // TODO:是否和芯片相关,要增加枚举??? 待优化!!!
            this->_usbDevice.dev = dev;
            this->_usbDevice.inEndPoint = 0x82;
            this->_usbDevice.outEndPoint = 0x02; // TODO:wait 9329
            return true;
        }
        return false;
    }

    bool openUsbDevice(void)
    {
        if (!_usbDevice.dev) {
            printf("can't open usbDevice\n");
            return false;
        }

        auto ret = libusb_open(_usbDevice.dev, &_usbDevice.handle);
        if (LIBUSB_SUCCESS != ret) {
            // TODO:打开USB设备
            printf("failed open usbDevice\n");
            return false;
        }

        libusb_detach_kernel_driver(_usbDevice.handle, 0);
        libusb_claim_interface(_usbDevice.handle, 0);

        return true;
    }

    bool setTransmitParam(uint8_t baudrate, uint8_t dataBits, uint8_t parity, uint8_t stopBits, uint8_t interval)
    {
        ERROR_CHECK(baudrate > B115200 || baudrate < B300, "baudrate error\n");
        ERROR_CHECK(parity > P_NONE || parity < P_ODD, "parity error\n");
        ERROR_CHECK(stopBits > STOP_2 || stopBits < STOP_1, "stopBits error\n");
        ERROR_CHECK(dataBits > BIT_8 || dataBits < BIT_5, "dataBits error\n");

        char buf[32] = { 0 };
        char m_str[5] = { 0 };

        m_str[4] = interval;
        m_str[0] |= 0x01;

        switch (baudrate) {
        case B300:
            m_str[2] = 0x80;
            m_str[3] = 0xD9;
            break;
        case B600:
            m_str[2] = 0x81;
            m_str[3] = 0x64;
            break;
        case B1200:
            m_str[2] = 0x81;
            m_str[3] = 0xB2;
            break;
        case B2400:
            m_str[2] = 0x81;
            m_str[3] = 0xD9;
            break;
        case B4800:
            m_str[2] = 0x82;
            m_str[3] = 0x64;
            break;
        case B9600:
            m_str[2] = 0x82;
            m_str[3] = 0xB2;
            break;
        case B14400:
            m_str[2] = 0x82;
            m_str[3] = 0xCC;
            break;
        case B19200:
            m_str[2] = 0x82;
            m_str[3] = 0xD9;
            break;
        case B28800:
            m_str[2] = 0x83;
            m_str[3] = 0x30;
            break;
        case B38400:
            m_str[2] = 0x83;
            m_str[3] = 0x64;
            break;
        case B57600:
            m_str[2] = 0x83;
            m_str[3] = 0x98;
            break;
        case B76800:
            m_str[2] = 0x83;
            m_str[3] = 0xB2;
            break;
        case B115200:
            m_str[2] = 0x83;
            m_str[3] = 0xCC;
            break;
        default:
            m_str[2] = 0x82;
            m_str[3] = 0xCC;
            break;
        }

        switch (parity) {
        case P_ODD:
            m_str[1] &= ~0x30;
            m_str[1] |= 0x08;
            break;
        case P_EVEN:
            m_str[1] &= ~0x10;
            m_str[1] |= 0x18;
            break;
        case P_SPC:
            m_str[1] |= 0x38;
            break;
        case P_NONE:
            m_str[1] &= ~0x08;
            break;
        default:
            m_str[1] &= ~0x08;
            break;
        }

        switch (stopBits) {
        case STOP_1:
            m_str[1] |= 0x04;
            break;
        case STOP_2:
            m_str[1] &= ~0x04;
            break;
        default:
            m_str[1] |= 0x04;
            break;
        }

        switch (dataBits) {
        case BIT_5:
            m_str[1] &= ~0x3;
            break;
        case BIT_6:
            m_str[1] &= ~0x3;
            m_str[1] |= 0x1;
            break;
        case BIT_7:
            m_str[1] &= ~0x3;
            m_str[1] |= 0x2;
            break;
        case BIT_8:
            m_str[1] |= 0x3;
            break;

        default:
            break;
        }

        m_str[0] = 0xFF;
        m_str[1] |= 0xC0;

        memcpy(buf, m_str, sizeof(m_str));

        return libusb_control_transfer(_usbDevice.handle,
                   LIBUSB_ENDPOINT_OUT + LIBUSB_REQUEST_TYPE_CLASS + LIBUSB_RECIPIENT_INTERFACE,
                   HID_REPORT_SET,
                   HID_RT_OUTPUT << 8,
                   0x00,
                   (unsigned char*)buf,
                   sizeof(buf),
                   _usbDevice.writeTimeout)
            == sizeof(buf);
    }

    bool send(std::vector<uint8_t>& data)
    {
        if (!_usbDevice.isOpen()) {
            printf("usb device not open\n");
            return false;
        }

        std::size_t packCount = data.size() / 31;
        std::size_t left = data.size() % 31;

        std::size_t sendBytes = 0;

        for (int i = 0; i < packCount; i++) {
            int len = 0;
            std::vector<uint8_t> pack({ 31 });
            pack.insert(pack.end(), data.begin() + sendBytes, data.begin() + sendBytes + 31);

            auto ret = libusb_interrupt_transfer(_usbDevice.handle,
                _usbDevice.outEndPoint,
                pack.data(),
                pack.size(),
                &len,
                _usbDevice.writeTimeout);

            if (ret == -ETIMEDOUT || len != 32) {
                printf("send return!\n");
                return false;
            }
            sendBytes += len - 1;
        }

        if (left) {
            int len = 0;
            std::vector<uint8_t> pack({ static_cast<uint8_t>(left) });
            pack.insert(pack.end(), data.begin() + sendBytes, data.end());

            auto r = libusb_interrupt_transfer(_usbDevice.handle,
                _usbDevice.outEndPoint,
                pack.data(),
                pack.size(),
                &len,
                _usbDevice.writeTimeout);

            if (r == -ETIMEDOUT || len != left + 1) {
                return false;
            }
            sendBytes += len - 1;
        }

        return sendBytes == data.size();
    }

    std::vector<uint8_t> recv(void)
    {
        std::vector<uint8_t> recvData;
        if (!_usbDevice.isOpen()) {
            printf("usb device not open\n");
            return recvData;
        }

        auto buf = std::make_unique<unsigned char[]>(512);

        int len = 0;

        auto ret = libusb_interrupt_transfer(_usbDevice.handle,
            _usbDevice.inEndPoint,
            buf.get(),
            0x20,
            &len,
            _usbDevice.readTimeout);
        if (ret != LIBUSB_SUCCESS || len == 0) {
            printf("can't recv some data\n");
            return recvData;
        }

        if (len) {
            for (int i = 1; i <= buf[0]; i++) {
                recvData.push_back(buf[i]);
            }
        }

        return recvData;
    }

    bool closeUsbDevice(void)
    {
        if (!_usbDevice.isOpen()) {
            printf("usb device not open\n");
            return false;
        }

        libusb_release_interface(_usbDevice.handle, 0);
        libusb_close(_usbDevice.handle);

        return _usbDevice.close();
    }
};
